import { BYTEDANCE, WECHAT } from "cc/env";

/**
 * @author Simon <6956319@qq.com> 
 * @version 1.1.10
 * @copyright Simon.Wang 2024-? SDK可以用在您的自己的游戏项目中自由使用，但绝不可传播、转售。违者必究！有问题加微：chaneywang
 * @description 本SDK仅仅使用在Cocos Creator 3.x的项目中，2.x请使用“说明”文件夹内对应其他版本
 */

export namespace MiniGameSdk {

    export const VERSION = '1.1.10';

    function printVersion() {
        Logger.log(`MiniGameSdk version: ${VERSION}. Cocos Creator 3.0.0 to 3.8.x compatible, wechat and bytedance compatible, if there are any problems, please contact wechat: chaneywang`);
    }

    class Logger {
        static log(...args: any[]) { console.log('[MiniGameSdk]', ...args); }
        static warn(...args: any[]) { console.warn('[MiniGameSdk]', ...args); }
        static error(...args: any[]) { console.error('[MiniGameSdk]', ...args); }
    }

    class Platform {
        private static _instance: Platform;

        static get instance() {
            if (!this._instance) {
                printVersion();
                this._instance = new Platform();
            }
            return this._instance;
        }

        private _hd: any;
        private _sys: any;

        private constructor() {
            if (isWechat()) {
                this._hd = window['wx'];
                Logger.log('Wechat platform!');
            } else if (isBytedance()) {
                this._hd = window['tt'];
                Logger.log('Bytedance platform!');
            } else {
                this._hd = null;
                Logger.warn('Unsupport platform!');
            }

            this._sys = this._hd?.getSystemInfoSync();
        }

        get windowWidth() {
            return this._sys ? this._sys.windowWidth : 0;
        }

        get windowHeight() {
            return this._sys ? this._sys.windowHeight : 0;
        }

        createInterstitialAd(param: { adUnitId: string }) {
            if (!param.adUnitId) {
                Logger.warn('param.adUnitId is invalid or empty!');
                return null;
            }

            if (this._hd && !this._hd.createInterstitialAd) {
                Logger.warn('Unsupport interstitial AD!');
                return null;
            }
            return this._hd ? this._hd.createInterstitialAd(param) : null;
        }

        createBannerAd(param: { adUnitId: string, adIntervals: number, style: { left: number, top: number, width: number } }) {
            if (!param.adUnitId) {
                Logger.warn('param.adUnitId is invalid or empty!');
                return null;
            }

            if (this._hd && !this._hd.createBannerAd) {
                Logger.warn('Unsupport banner AD!');
                return null;
            }

            return this._hd ? this._hd.createBannerAd(param) : null;
        }

        createCustomAd(param: { adUnitId: string, style: { left: number, top: number, fixed: boolean } }) {
            if (!param.adUnitId) {
                Logger.warn('param.adUnitId is invalid or empty!');
                return null;
            }

            if (this._hd && !this._hd.createCustomAd) {
                Logger.warn('Unsupport custom AD!');
                return null;
            }

            return this._hd ? this._hd.createCustomAd(param) : null;
        }

        createRewardedVideoAd(param: { adUnitId: string, multiton: boolean, multitonRewardMsg: string[], multitonRewardTimes: number }) {
            if (!param.adUnitId) {
                Logger.warn('param.adUnitId is invalid or empty!');
                return null;
            }

            if (this._hd && !this._hd.createRewardedVideoAd) {
                Logger.warn('Unsupport rewarded video AD!');
                return null;
            }

            if (isWechat()) {
                return this._hd ? this._hd.createRewardedVideoAd({ adUnitId: param.adUnitId }) : null;
            } else if (isBytedance()) {
                return this._hd ? this._hd.createRewardedVideoAd(param) : null;
            }

            return null;
        }
    }

    export interface ISize {
        width: number;
        height: number;
    }

    export interface IPosition {
        top: number;
        left: number;
    }

    export function isWechat(): boolean {
        return WECHAT;
    }

    export function isBytedance(): boolean {
        return BYTEDANCE;
    }

    function getSysWinSize(): ISize {
        return { width: Platform.instance.windowWidth, height: Platform.instance.windowHeight };
    }

    class AdBase {
        private _adUid: string = '';

        protected constructor(adUid: string) {
            if (!adUid || adUid.length === 0) {
                Logger.warn('adUid is invalid or empty!');
                return;
            }

            if (isWechat() && !adUid.startsWith('adunit-')) {
                Logger.warn('In wechat, adUid must start with adunit-');
                return;
            }

            this._adUid = adUid;
        }

        get aduid() {
            return this._adUid;
        }
    }

    class ADInterstitial extends AdBase {
        private _interstitial: any;

        constructor(adUid: string) {
            super(adUid);
        }

        show() {
            if (this._interstitial) {
                this._interstitial.load();
            } else {
                this._interstitial = Platform.instance.createInterstitialAd({ adUnitId: this.aduid });
                this._interstitial?.onLoad(() => {
                    Logger.log('load interstitial ad success');
                    this._interstitial?.show().catch((err: any) => {
                        Logger.log('catch interstitial ad error:', err);
                    });
                });

                this._interstitial?.onError((err: any) => {
                    Logger.log('interstitial ad on error:', err);
                });
            }
        }

        destory() {
            this._interstitial?.destroy();
        }
    }

    class ADBanner extends AdBase {
        private _banner: any;

        constructor(adUid: string, positionData: boolean | IPosition, bannerWidth: number = 300, autoShow: boolean = false) {
            super(adUid)
            this.create(autoShow, bannerWidth, positionData);
        }

        private create(autoShow: boolean, bannerWidth: number, param: boolean | IPosition) {
            if (!isWechat() && !isBytedance()) {
                this._banner = null;
                Logger.log('Only wechat and bytedance support AD');
                return;
            }

            this.destroy();

            const winSize = getSysWinSize();

            const height = bannerWidth * 0.4;
            let top = 0, left = 0;

            if (typeof param === "boolean") {
                left = (winSize.width - bannerWidth) / 2
                top = param ? 5 : (winSize.height - height);
            } else {
                left = param.left;
                top = param.top;
            }

            const params = {
                adUnitId: this.aduid,
                adIntervals: 30,
                style: { left: left, top: top, width: bannerWidth }
            }

            this._banner = Platform.instance.createBannerAd(params);

            this._banner?.onError((err: any) => {
                Logger.log('ad banner error:', err);
            });

            this._banner?.onLoad(() => {
                autoShow && this._banner.show();
            });
        }

        show() {
            this._banner?.show();
        }

        hide() {
            this._banner?.hide();
        }

        destroy() {
            this._banner?.destroy();
        }
    }

    class ADCustom extends AdBase {
        private _adCustom: any;

        constructor(adUid: string, top: number = 0, left: number = 0, scale: number = 1.0) {
            super(adUid);
            this.createCustomAd(top, left, scale);
        }

        private createCustomAd(top: number, left: number, scale: number) {
            if (!isWechat()) {
                this._adCustom = null;
                Logger.log('Only wechat support Custom Ad');
                return;
            }

            this.destroy();

            const param = {
                adUnitId: this.aduid,
                style: { left: left, top: top, fixed: true }
            }

            this._adCustom = Platform.instance.createCustomAd(param);
            this._adCustom?.onError((err: any) => {
                Logger.log('ad custom error:', err);
            });
        }
        show() {
            this._adCustom?.show();
        }

        hide() {
            this._adCustom?.hide();
        }

        destroy() {
            this._adCustom?.destroy();
        }
    }


    /**
     * 视频广告用户点击行为结果
     */
    export enum EAdVideoResult {
        /**
         * 用户看完了广告，游戏可发放奖励。
         */
        ACCEPT,

        /**
         * 用户中途关闭了广告，即未看完状态。不可发放奖励。
         */
        REJECT,

        /**
         * 广告组件内部发生了错误。不可发放奖励。
         */
        ERROR,
    }

    class ADVideo extends AdBase {
        private _adVideo: any;
        private _isShowed: boolean = false;

        constructor(adUid: string) {
            super(adUid);
        }

        show(onResult: (res: EAdVideoResult, count: number) => void, target?: any, maxVideoCount: number = 3): void {
            const onResultCallback = (state: EAdVideoResult, count: number) => {
                onResult?.call(target, state, count);
                this._isShowed = false;
            }

            if (!isWechat() && !isBytedance()) {
                onResultCallback(EAdVideoResult.ACCEPT, 1);
                this._adVideo = null;
                this._isShowed = false;
                return;
            }

            if (this._isShowed) {
                console.log('Ad video is loading or showing, just wait for show');
                return;
            }

            this._isShowed = true;

            const onAdVideoClosed = (res: any) => {
                this._adVideo?.offClose(onAdVideoClosed);
                if (isWechat()) {
                    res && res.isEnded || res === undefined ?
                        onResultCallback(EAdVideoResult.ACCEPT, 1) :
                        onResultCallback(EAdVideoResult.REJECT, 0);
                } else if (isBytedance()) {
                    const result = res as { isEnded: boolean, count: number };
                    result && result.count > 0 ?
                        onResultCallback(EAdVideoResult.ACCEPT, result.count) :
                        onResultCallback(EAdVideoResult.REJECT, 0);
                }
            }

            this._adVideo?.offClose(onAdVideoClosed);

            const param = {
                adUnitId: this.aduid,
                multiton: true,
                multitonRewardMsg: ['多1次奖励', '再多一次奖励', '再多一次奖励'],
                multitonRewardTimes: maxVideoCount,
            }

            this._adVideo = Platform.instance.createRewardedVideoAd(param);
            this._adVideo?.onLoad(() => {
                Logger.log('Ad load success');
            });

            this._adVideo?.onError((err: { errMsg: string, errCode: number }) => {
                Logger.log('Ad video error:', err);
                onResultCallback(EAdVideoResult.ERROR, 0);
            });

            this._adVideo?.onClose(onAdVideoClosed);

            this._adVideo?.show().catch(() => {
                this._adVideo?.load().then(() =>
                    this._adVideo?.show()).catch((err: { errMsg: string, errCode: number }) => {
                        Logger.log('Catch video ad error:', err);
                        onResultCallback(EAdVideoResult.ERROR, 0);
                    });
            });
        }

        destory() {
            this._adVideo?.destory();
        }
    }

    export enum EAdBannerLocation {
        /**
         * 屏幕顶部
         */
        TOP,

        /**
         * 屏幕底部
         */
        BOTTOM,
    }

    export class AdvertManager {

        private static _instance: AdvertManager;

        static get instance(): AdvertManager {
            if (!AdvertManager._instance) {
                AdvertManager._instance = new AdvertManager();
            }
            return AdvertManager._instance;
        }

        private _video: ADVideo;
        private _interstitial: ADInterstitial;
        private _customs: Record<string, ADCustom> = {};
        private _banners: Record<string, ADBanner> = {};

        private constructor() {

        }

        /**
         * 预加载横幅广告，默认不会显示。必须手动调用ShowBanner才会显示。可以创建多个，只要传入不同的广告UID即可
         * 推荐：一般情况，全局有一个就行了，太多占用内存。
         * @param adUid 广告UID
         * @param location 位置有两种情况：1、可以传入枚举值，默认上方; 2、可以自定义位置传入IPosition，注意IPosition中的top和left跟平台的top,left是一致（没有乘以设备像素比ratio），需要开发者自己调试位置
         * @param scale 默认为跟屏幕一样的宽度，可以通过设置缩放比例来调整大小。当然，平台有规定最大或最小宽度，函数内部会自动计算。
         */
        public loadBanner(adUid: string, location: EAdBannerLocation | IPosition = EAdBannerLocation.TOP, scale: number = 1.0) {
            if (this._banners[adUid]) {
                Logger.log(`banner ad ${adUid} has been loaded.`);
                return;
            }

            const winSize = getSysWinSize();
            let width = winSize.width * scale;
            width = width < 300 ? 300 : width;
            width = width > winSize.width ? winSize.width : width;
            this._banners[adUid] = typeof location === 'number' ?
                new ADBanner(adUid, location === EAdBannerLocation.TOP, width, false) :
                new ADBanner(adUid, location, width, false);
        }

        /**
         * 显示横幅广告
         * @param adUid 广告单元id
         */
        public showBanner(adUid: string) {
            if (this._banners[adUid]) {
                this._banners[adUid].show();
            } else {
                Logger.warn(`you must call loadBanner(${adUid}) first!`);
            }
        }

        /**
         * 隐藏横幅广告
         * @param adUid 广告单元id
         */
        public hideBanner(adUid: string) {
            if (this._banners[adUid]) {
                this._banners[adUid].hide();
            } else {
                Logger.warn(`not load custom ad ${adUid}, do nothing.`);
            }
        }

        /**
         * 销毁横幅广告
         * @param adUid 广告单元id
         */
        public destroyBanner(adUid: string) {
            if (this._banners[adUid]) {
                this._banners[adUid].destroy();
                delete this._banners[adUid];
            } else {
                Logger.warn(`not load custom ad ${adUid}, destroy nothing.`);
            }
        }

        /**
         * 弹出插屏广告
         * @param adUid 广告单元id
         */
        public showInterstitial(adUid: string) {
            if (this._interstitial && this._interstitial.aduid === adUid) {
                this._interstitial.show();
            } else {
                this._interstitial?.destory();
                this._interstitial = new ADInterstitial(adUid);
                this._interstitial.show();
            }
        }

        /**
         * 加载原生模板广告，不会显示。只有你在调用showCustom时才会显示。
         * 由于原生模板广告在微信服务后端可以定制宽度大小，个数，缩放比例等，所以位置调整要根据设置的宽度来定。抖音不支持本函数，会调用无效！
         * @param adUid 广告ID
         * @param location 可以自定义位置传入IPosition，注意IPosition中的top和left跟平台的top,left是一致（没有乘以设备像素比ratio），需要开发者自己调试位置
         * @param scale 缩放比例，默认是1，即不缩放。这个缩放并不是自己填，而是根据微信MP后台你配置的原生模板广告的缩放比例填，目前有100%，90%，80%三种，一般情况不用修改。若有后台修改，记得传入值，例如90%就传入0.9。
         */
        public loadCustom(adUid: string, location: IPosition = { top: 0, left: 0 }, scale: number = 1) {
            if (this._customs[adUid]) {
                Logger.log(`custom ad ${adUid} has been loaded.`);
                return;
            }

            this._customs[adUid] = new ADCustom(adUid, location.top, location.left, scale);
        }

        /**
         * 显示自定义广告。
         * @param adUid 广告的唯一标识符。使用此标识符来查找和显示特定的自定义广告。如果不传入，默认显示第一个。
         * 
         * 此方法尝试根据提供的adUid显示一个自定义广告。如果给定的adUid对应的自定义广告已加载，
         * 则调用该广告的显示方法。如果广告未加载，则在控制台输出警告信息。
         */
        public showCustom(adUid: string) {
            if (!adUid) {
                Logger.warn('invalid custom adUid, can not show!');
                return;
            }

            if (this._customs[adUid]) {
                this._customs[adUid].show();
            } else {
                Logger.warn(`You have not load ${adUid} of Custom AD, can not show!`);
            }
        }

        /**
         * 隐藏指定的自定义广告单元
         * 
         * 此方法用于隐藏通过广告单元标识符（adUid）指定的自定义广告。如果指定的广告单元已加载并显示，
         * 则将其隐藏；如果广告单元未加载，则在控制台输出警告信息。
         * 
         * @param adUid 广告单元标识符，用于唯一标识一个自定义广告单元。
         */
        public hideCustom(adUid: string) {
            if (this._customs[adUid]) {
                this._customs[adUid].hide();
            } else {
                Logger.warn(`You have not load ${adUid} of Custom AD, can not hide!`);
            }
        }

        /**
         * 删除指定的原生模板广告单元
         * @param adUid 广告ID
         */
        public destroyCustom(adUid: string) {
            if (this._customs[adUid]) {
                this._customs[adUid].destroy();
                delete this._customs[adUid];
            } else {
                Logger.warn(`You have not load ${adUid} of Custom AD, destroy nothing!`);
            }
        }

        /**
         * 由于微信和抖音视频广告机制不同，微信可以看的视频广告个数只有0和1个，抖音平台则可以看0~maxVideoCount
         * @param adUid 广告ID。如果与上一次UID不同，则内部会重新创建实例。开发者完全不用关心这个细节。
         * @param onVideoResult 两个参数：第一个res是EAdVideoResult定义，第二count是用户看了多少个视频广告。 
         * @param target onVideoResult的拥有者
         * @param maxVideoCount 最大视频个数。默认是3，仅对抖音平台生效。微信平台看完视频count的结果永远是1或0
         */
        public showVideo(adUid: string, onVideoResult: (res: EAdVideoResult, count: number) => void, target?: any, maxVideoCount: number = 3) {
            if (this._video && this._video.aduid === adUid) {
                this._video.show(onVideoResult, target, maxVideoCount);
            } else {
                this._video?.destory();
                this._video = new ADVideo(adUid);
                this._video.show(onVideoResult, target, maxVideoCount);
            }
        }

        /**
         * 销毁内部所有实例，清空内存
         */
        public destroyAll() {
            this._interstitial?.destory();
            this._interstitial = null;

            this._video?.destory();
            this._video = null;

            if (this._customs) {
                for (let val in this._customs) {
                    this._customs[val]?.destroy();
                }
                this._customs = {};
            }

            if (this._banners) {
                for (let val in this._banners) {
                    this._banners[val]?.destroy();
                }
                this._banners = {};
            }
        }
    }

    export enum EGameClubIcon {
        /** 绿色图标 */
        GREEN = 'green',

        /** 红色图标 */
        WHITE = 'white',

        /** 有黑色圆角背景的白色图标 */
        DARK = 'dark',

        /** 有白色圆角背景的绿色图标 */
        LIGHT = 'light'
    }

    export class GameClub {
        private static _instance: GameClub;

        static get instance(): GameClub {
            if (!this._instance) {
                this._instance = new GameClub();
            }
            return this._instance;
        }

        private _club: any;

        private constructor() {

        }

        /**
         * 创建游戏圈按钮
         * @param icon 
         * @param position 
         * @param size 
         * @param openLink 
         */
        create(icon: EGameClubIcon = EGameClubIcon.GREEN, position: IPosition = { top: 0, left: 0 }, size: ISize = { width: 40, height: 40 }, openLink?: string) {
            if (isWechat()) {
                // @ts-ignore
                this._club = wx.createGameClubButton({
                    icon: icon,
                    style: {
                        left: position.left,
                        top: position.top,
                        width: size.width,
                        height: size.height
                    },
                    openlink: openLink
                });
            }
        }

        show() {
            this._club?.show();
        }

        hide() {
            this._club?.hide();
        }

        destory() {
            this._club?.destroy();
        }
    }

    class PageManager {
        private _openlink: string;
        private _manager: any;
        protected wx: any;

        constructor(openlink: string) {
            this._openlink = openlink;
            this.wx = window['wx'];
            if (!this.wx) {
                console.warn(`Not wechat context, PageManager will not work!`);
                return;
            }

            if (!this.wx.createPageManager) {
                console.warn(`wx.createPageManager not exist, wechat sdk version must >= 3.8.6`);
                return;
            }

            this._manager = this.wx.createPageManager();
        }

        get openlink() {
            return this._openlink;
        }

        async load() {
            if (!this._manager) {
                console.warn(`PageManager not exist, load() will not work!`);
                return;
            }

            console.log('load:', this.openlink)

            await this._manager?.load({ openlink: this.openlink });
        }

        async show() {
            if (!this._manager) {
                console.warn(`PageManager not exist, show() will not work!`);
                return;
            }
            return await this._manager?.show();
        }

        destroy() {
            this._manager?.destroy();
        }

        protected on(event: string, callback: Function) {
            if (!this._manager) {
                console.warn(`PageManager not exist, on() will not work!`);
                return;
            }
            this._manager?.on(event, callback);
        }
    }

    export class RecommendPageManager extends PageManager {
        static readonly EventType = {
            ON_READY: 'ready',
            ON_SHOW: 'show',
            ON_DESTROY: 'destroy',
            ON_ERROR: 'error'
        }

        constructor() {
            super('TWFRCqV5WeM2AkMXhKwJ03MhfPOieJfAsvXKUbWvQFQtLyyA5etMPabBehga950uzfZcH3Vi3QeEh41xRGEVFw');
        }

        onReady(callback: () => void) {
            this.on(RecommendPageManager.EventType.ON_READY, () => {
                callback?.();
            });
        }

        onShow(callback: () => void) {
            this.on(RecommendPageManager.EventType.ON_SHOW, () => {
                callback?.();
            });
        }

        onDestroy(callback: (isRecommended: boolean) => void) {
            this.on(RecommendPageManager.EventType.ON_DESTROY, (res: { isRecommended: boolean }) => {
                callback?.(res.isRecommended);
            });
        }

        onError(callback: (code: number, msg: string) => void) {
            this.on(RecommendPageManager.EventType.ON_ERROR, (res: { code: number, msg: string }) => {
                callback?.(res.code, res.msg);
            });
        }
    }

    /**
     * 振动类型
     */
    export enum EVirbrateType {
        /**
         * 短振动
         */
        SHORT,

        /**
         * 长振动
         */
        LONG
    }

    /**
     * 平台常用API合集
     */
    export class API {
        private static _loginCode: string = null;
        private static _loginAnonymousCode: string = null;
        private static _hasInitWechatCloudFunction: boolean = false;

        /**
         * 分享app给朋友，微信小游戏分享是没有onSuccess回调的。
         * @param title 标题
         * @param description 细节描述信息 
         * @param imageUrl 图片地址
         * @param query 查询信息
         * @param onSuccess 抖音会回调，微信不会回调
         */
        static shareAppToFriends(title: string, description: string = '', imageUrl?: string, query?: string, onSuccess?: () => void) {
            if (isWechat()) {
                try {
                    //@ts-ignore
                    wx.shareAppMessage({
                        title: title,
                        imageUrl: imageUrl,
                        query: query,
                    });
                } catch (err) {
                    Logger.log(`share faild: ${err}`);
                }
            }

            if (isBytedance()) {
                //@ts-ignore
                tt.shareAppMessage({
                    title: title,
                    desc: description,
                    imageUrl: imageUrl ?? '',
                    query: query ?? '',
                    success(res: any) {
                        Logger.log('share success:', res);
                        onSuccess?.();
                    },
                    fail(res: any) {
                        Logger.log('share fail:', res);
                    }
                });
            }
        }

        /**
         * 显示提示信息
         * @param title 标题
         * @param duration 时长（单位：秒）
         * @returns 
         */
        static showToast(title: string, duration: number = 2) {
            if (isWechat()) {
                // @ts-ignore
                wx.showToast({
                    title: title,
                    icon: 'success',
                    duration: duration * 1000
                });
            }

            if (isBytedance()) {
                //@ts-ignore
                tt.showToast({
                    title: title,
                    duration: duration * 1000,
                    success(res: any) {
                        Logger.log(`${res}`);
                    },
                    fail(res: any) {
                        Logger.log(`showToast调用失败`);
                    },
                });
            }
        }

        /**
         * 设备震动效果，默认为短震动。注意：可能一些机型不会生效，具体看平台方的说明
         * @param type MiniGameSdk.API.EVirbrateType
         */
        static vibrate(type: EVirbrateType = EVirbrateType.SHORT) {
            if (isWechat()) {
                switch (type) {
                    case EVirbrateType.SHORT:
                        //@ts-ignore
                        wx.vibrateShort({
                            success(res: any) {
                                Logger.log('vibrate success:', res);
                            },
                            fail(res: any) {
                                Logger.log('vibrateShort failed', res);
                            },
                        });
                        break;
                    case EVirbrateType.LONG:
                        //@ts-ignore
                        wx.vibrateLong({
                            success(res: any) {
                                Logger.log('vibrate success', res);
                            },
                            fail(res: any) {
                                Logger.log(`vibrateLong failed`, res);
                            },
                        });
                        break;
                    default:
                        break;
                }
            }

            if (isBytedance()) {
                switch (type) {
                    case EVirbrateType.SHORT:
                        //@ts-ignore
                        tt.vibrateShort({
                            success(res: any) {
                                Logger.log('vibrate success:', res);
                            },
                            fail(res: any) {
                                Logger.log('vibrateShort failed', res);
                            },
                        });
                        break;
                    case EVirbrateType.LONG:
                        //@ts-ignore
                        tt.vibrateLong({
                            success(res: any) {
                                Logger.log('vibrate success', res);
                            },
                            fail(res: any) {
                                Logger.log(`vibrateLong failed`, res);
                            },
                        });
                        break;
                    default:
                        break;
                }
            }
        }

        /**
         * 重启小游戏
         */
        static reboot() {
            if (isWechat()) {
                //@ts-ignore
                wx.restartMiniProgram({
                    success: () => {
                        Logger.log('restart success');
                    },

                    fail: () => {
                        Logger.log('restart failed');
                    }
                })
            }

            if (isBytedance()) {
                try {
                    // @ts-ignore
                    tt.restartMiniProgramSync();
                } catch (error) {
                    Logger.log(`restartMiniProgramSync`, error);
                }
            }
        }

        /**
         * 退出小游戏
         */
        static exit() {
            if (isWechat()) {
                //@ts-ignore
                wx.exitMiniProgram({
                    success: () => {
                        Logger.log('exit success');
                    },
                    fail: () => {
                        Logger.log('exit failed');
                    }
                });
            }

            if (isBytedance()) {
                // @ts-ignore
                tt.exitMiniProgram({
                    success(res: any) {
                        Logger.log("exit success:", res?.data);
                    },
                    fail(res: any) {
                        Logger.log("exit fail:", res?.errMsg);
                    },
                });
            }
        }

        /**
         * 显示转发按钮。通常在刚进入游戏的时候调用。
         * 主要是打开平台“...”这个按钮里面的分享菜单，一般默认是关闭的，需要调用这个函数打开。可以让用户分享你的游戏入口。
         */
        static showShareMenu() {
            if (isWechat()) {
                //@ts-ignore
                wx.showShareMenu({
                    withShareTicket: true,
                    menus: ['shareAppMessage', 'shareTimeline'],
                    success: () => { },
                    fail: () => { },
                    complete: () => { }
                });
            }

            if (isBytedance()) {
                //@ts-ignore
                tt.showShareMenu({
                    success(res: any) {
                        Logger.log("show menu is showing");
                    },
                    fail(err: any) {
                        Logger.log("showShareMenu:", err.errMsg);
                    },
                    complete(res: any) {
                        Logger.log("showShareMenu complete");
                    },
                });
            }
        }

        /**
         * 微信小游戏：跳转到另外一款小游戏
         * 抖音小游戏：跳转到指定的视频界面
         * @param targetId 微信小游戏appid或者视频界面
         */
        static navigateTo(targetId: string, onSuccess?: () => void) {
            if (isWechat()) {
                // @ts-ignore
                wx.navigateToMiniProgram({
                    appId: targetId,
                    extraData: {
                        foo: 'bar'
                    },
                    envVersion: 'develop',
                    success(res: any) {
                        onSuccess?.();
                    }
                });
            }

            if (isBytedance()) {
                // @ts-ignore
                tt.navigateToVideoView({
                    videoId: targetId,
                    success: (res: any) => {
                        onSuccess?.();
                    },
                    fail: (err: any) => {
                        Logger.log("bytedance navigateToVideoView fail", err);
                    },
                });
            }
        }


        /**
         * 小游戏平台登录功能。微信返回code，抖音返回code和anonymousCode。用于登录的凭证，需要把这个code传回你的服务器程序中去调用code2Session
         * @param callback (code, anonymousCode) 第一个参数为code，微信和抖音都支持；第二个参数为匿名设备ID，仅抖音支持，失败都返回null
         */
        static login(callback: (code: string, anonymousCode: string) => void) {
            let loginPlatform = () => {
                if (isWechat()) {
                    //@ts-ignore
                    wx.login({
                        success: (res: { code: any; errMsg: any; }) => {
                            if (res.code) {
                                API._loginCode = res.code;
                                API._loginAnonymousCode = null;
                                callback?.(API._loginCode, API._loginAnonymousCode);
                            } else {
                                Logger.log('login error:', res.errMsg)
                            }
                        },

                        fail: () => {
                            API._loginCode = null;
                            API._loginAnonymousCode = null;
                            callback?.(API._loginCode, API._loginAnonymousCode);
                            Logger.log('login fail')
                        }
                    });
                } else if (isBytedance()) {
                    //@ts-ignore
                    tt.login({
                        force: true,
                        success(res: any) {
                            Logger.log(`login ${res.code} ${res.anonymousCode}`);
                            if (res.code) {
                                API._loginCode = res.code?.toString();
                                API._loginAnonymousCode = res.anonymousCode?.toString();
                                callback?.(API._loginCode, API._loginAnonymousCode);
                            } else {
                                Logger.log('login error:', res.errMsg)
                            }
                        },
                        fail(res: any) {
                            API._loginCode = null;
                            API._loginAnonymousCode = null;
                            callback?.(API._loginCode, API._loginAnonymousCode);
                            Logger.log(`login fail`, res);
                        },
                    });
                } else {
                    API._loginCode = null;
                    API._loginAnonymousCode = null;
                    callback?.(API._loginCode, API._loginAnonymousCode);
                    Logger.log('not mini game platform, login codes are all null');
                }
            }



            if (!API._loginCode) {
                loginPlatform();
            } else {
                if (isWechat()) {
                    //@ts-ignore
                    wx.checkSession({
                        success() {
                            Logger.log(`session is valid, use current code:`, API._loginCode);
                            callback?.(API._loginCode, API._loginAnonymousCode);
                        },
                        fail() {
                            Logger.log(`session expired`);
                            loginPlatform();
                        }
                    });
                } else if (isBytedance()) {
                    //@ts-ignore
                    tt.checkSession({
                        success() {
                            Logger.log(`session is valid, user current code: ${API._loginCode}, ${API._loginAnonymousCode}`);
                            callback?.(API._loginCode, API._loginAnonymousCode);
                        },
                        fail() {
                            Logger.log(`session expired`);
                            loginPlatform();
                        },
                    });

                } else {
                    Logger.log('not mini game platform, login null');
                    callback?.(null, null);
                }
            }
        }

        /**
         * 调用微信云函数。由于参数需要自定义，所以为any，需要自行解释。函数只完成通道和处理一场的作用
         * @param callback 返回云函数调用结果。需要检查返回参数是否为空，失败的时候为空
         * @param name 云函数的名字
         * @param data 云函数的内容
         */
        static callWechatCloudFunction(callback: (res: any) => void, name: string, data: {}) {
            if (!isWechat()) {
                Logger.log('Not wechat platform, not support callWechatCloudFunction');
                return;
            }

            this.login((code: string, anonymousCode: string) => {
                if (!API._hasInitWechatCloudFunction) {
                    //@ts-ignore
                    wx.cloud.init();
                    API._hasInitWechatCloudFunction = true;
                }
                //@ts-ignore
                wx.cloud.callFunction({
                    name: name,
                    data: data,
                    success: (res: any) => callback?.(res),
                    fail: (err: any) => {
                        Logger.log('wechat cloud function error:', err);
                        callback?.(null);
                    }
                });
            });
        }

        /**
         * 存储用户信息，数据量不能大。可以考虑用于分数排行榜。用户之间可共享排行数据。
         * @param key 
         * @param value 
         */
        static setUserCloudStorage(key: string, value: string) {
            if (isWechat()) {
                // @ts-ignore
                wx.setUserCloudStorage({
                    KVDataList: [{ key: key, value: value }],
                    success: () => Logger.log(`set cloud storage success:${key}, value:${value}`),
                    fail: (err: any) => Logger.log('set cloud storage error:', err)
                });
            }

            if (isBytedance()) {
                // @ts-ignore
                tt.setUserCloudStorage({
                    KVDataList: [{ key: key, value: value, }],
                    success: () => Logger.log(`set cloud storage success:${key}, value:${value}`),
                    fail: (err: any) => Logger.log('set cloud storage error:', err)
                });
            }
        }

        /**
         * 获取微信平台用户的头像和昵称。注意：需要在MP后台的隐私协议里面添加权限，并且需要授权用户同意
         * @param onSuccess 
         * @param onFail 
         * @returns 
         */
        static getWechatUserNameAndAvatar(onSuccess: (nickname: string, avatarUrl: string) => void, onFail: (message: string) => void) {
            if (!isWechat()) {
                return;
            }

            // @ts-ignore
            wx.getSetting({
                success(res: any) {
                    if (res.authSetting['scope.userInfo']) {
                        // @ts-ignore
                        wx.getUserInfo({
                            success: function (res) {
                                console.log('auth ok, directly getting');
                                const userInfo = res.userInfo;
                                onSuccess(userInfo.nickName, userInfo.avatarUrl);
                            },
                            fail: function () {
                                onFail('auth fail, need to get user info');
                            }
                        });
                    } else {
                        onFail('no auth');
                    }
                },

                fail(err: any) {
                    onFail('get setting error' + err,);
                }
            });
        }
    }


    /**
     * 抖音侧边栏专属接口
     */
    export class BytedanceSidebar {
        /**
         * 本游戏在抖音环境下启动监控，需要放在全局环境中，保证能第一时间启动。因为可能监听抖音失败（抖音小游戏官方的说明）！
         * @param onResult 包含一个boolean参数的函数
         * @param target 上述函数的拥有者，如果是类的成员函数，需要传入this。普通或匿名函数忽略即可。
         */
        static listenFromSidebar(onResult: (success: boolean) => void, target?: any) {
            if (!isBytedance()) {
                onResult?.call(target, false);
                return;
            }
            // @ts-ignore
            tt.onShow((res: any) => {
                Logger.log('onShow launch res:', res);
                if (res.scene === '011051') {
                    onResult?.call(target, true);
                    Logger.log('launch from sidebar');
                } else {
                    onResult?.call(target, false);
                    Logger.log('NOT launch from douyin sidebar!');
                }
            });

            // @ts-ignore
            let options = tt.getLaunchOptionsSync();
            if (options && options.scene === '011051') {
                onResult?.call(target, true);
            }
        }

        /**
         * 检测抖音侧边栏是否存在
         * @param onResult 包含一个boolean参数的函数
         * @param target 上述函数的拥有者，如果是类的成员函数，需要传入this。普通或匿名函数忽略即可。
         * @returns 
         */
        static checkSideBar(onResult: (success: boolean) => void, target?: any) {
            if (!isBytedance()) {
                onResult?.call(target, false);
                return;
            }

            //@ts-ignore
            tt.checkScene({
                scene: "sidebar",
                success: (res: any) => {
                    Logger.log("check scene success: ", res.isExist);
                    onResult?.call(target, <boolean>res.isExist);

                },
                fail: (res: any) => {
                    Logger.log("check scene fail:", res);
                    onResult?.call(target, false);
                }
            });
        }

        /**
         * 跳转到抖音侧边栏
         * @param onResult 包含一个boolean参数的函数
         * @param target 上述函数的拥有者，如果是类的成员函数，需要传入this。普通或匿名函数忽略即可。
         * @returns 
         */
        static navigateToSidebar(onResult: (success: boolean) => void, target?: any) {
            if (!isBytedance()) {
                Logger.log("not douyin platform!");
                onResult?.call(target, false);
                return;
            }

            // @ts-ignore
            tt.navigateToScene({
                scene: "sidebar",
                success: () => {
                    Logger.log("navigate success");
                    onResult?.call(target, true);
                },
                fail: (res: any) => {
                    Logger.log("navigate failed reason:", res);
                    onResult?.call(target, false);
                },
            });
        }
    }


    export class AdVideoWrapper {
        private static _defaultAdUid: string = null;

        static get defaultAdUid(): string {
            return AdVideoWrapper._defaultAdUid;
        }

        static set defaultAdUid(value: string) {
            AdVideoWrapper._defaultAdUid = value;
        }

        static show(onSuccess?: (count: number) => void, onRejected?: () => void, onError?: () => void, adUid?: string) {
            const uid = adUid ?? AdVideoWrapper.defaultAdUid;
            if (!uid) {
                Logger.warn('AdVideoWrapper show video with invalid AD');
                onError?.();
                return;
            }

            AdvertManager.instance.showVideo(uid, (res, count) => {
                switch (res) {
                    case EAdVideoResult.ACCEPT: onSuccess?.(count); break;
                    case EAdVideoResult.REJECT: onRejected?.(); break;
                    case EAdVideoResult.ERROR: break;
                    default: break;
                }
            });
        }
    }
}
